import openpyxl
import random
import ctypes
import PrimeNumberFun
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))


class Tools:
    sample_names = list()  # 样本名数组
    sample_matrix = list()  # 样本矩阵
    sample_dict_result = dict()  # 样本字典，记录原始生成树每一棵子树出现次数
    sample_dict_start = dict()
    sample_dis_dict = dict()  # 样本距离字典，记录中间子树与另一节点距离
    __count = 1  # 计数

    # 定义树节点
    class Tree(ctypes.Structure):
        pass

    Tree._fields_ = [("i", ctypes.c_int),
                     ("dis", ctypes.c_float),
                     ("left", ctypes.POINTER(Tree)),
                     ("right", ctypes.POINTER(Tree))]

    # 定义树节点空指针
    NULL = ctypes.POINTER(Tree)()

    # 工具类的开始函数
    def start(self):
        while True:
            try:
                file_path = input("请输入文件存储路径(windows文件路劲,带上后缀名)：")
                file_path = file_path.replace("\\", "/")
                work_book = openpyxl.load_workbook(file_path)  # 打开所需Excel文件
                break
            except Exception as ex:
                print(ex)
                continue
        self.init_sample(work_book)
        return file_path

        # print(self.sample_names)
        # for i in self.sample_matrix:
        #     print(i)
        # print(len(self.sample_matrix[5]))

    # 初始样本名数组和样本矩阵
    def init_sample(self, work_book):
        """
        :param work_book:excel 表工作簿
        :return: 无
        """
        work_sheet = work_book.active

        tap = 0
        for i in work_sheet:
            # 跳过第一行
            if tap == 0:
                tap += 1
                continue

            count = 0  # 计数
            temp = list()  # 空列表
            for j in i:
                if count == 0:
                    self.sample_names.append(j.value)
                else:
                    temp.append(int(j.value))
                count += 1
            self.sample_matrix.append(temp)

    # 计算距离矩阵
    def get_distance_matrix(self, rdm=False):
        """
        :param rdm:是否随机所有列,False表示不随机，True表示随机
        :return: 返回计算得到距离矩阵
        """
        random_list = list()
        if rdm:
            for i in range(0, len(self.sample_matrix[0])):
                random_list.append(random.randint(0, len(self.sample_matrix[0]) - 1))
        else:
            random_list = range(0, len(self.sample_matrix[0]))
        # print(random_list)
        # print(len(random_list))

        # 距离矩阵
        distance_matrix = list()

        length = len(self.sample_matrix)
        for i in range(0, length):
            temp = list()
            for j in range(0, length):
                if i > j:
                    temp.append(distance_matrix[j][i])
                elif i == j:
                    temp.append(0)
                else:
                    # 计算i行和j行的距离
                    a = 0
                    b = 0
                    c = 0
                    d = 0
                    m = 0
                    u = 0
                    n = 0
                    for k in random_list:
                        n1 = self.sample_matrix[i][k]
                        n2 = self.sample_matrix[j][k]
                        if n1 == 0 and n2 == 0:
                            d += 1
                        elif n1 == 1 and n2 == 1:
                            a += 1
                        elif n1 == 1 and n2 == 0:
                            b += 1
                        else:
                            c += 1
                    m = a + d
                    u = b + c
                    n = u + m
                    try:
                        temp.append(1 - a / (n - d))
                    except ZeroDivisionError as e:
                        print(e)
                        temp.append(0.5)
            distance_matrix.append(temp)
        return distance_matrix

    # 聚类算法
    def clustering_algorithm(self, matrix, fun):
        """
        :param matrix:二维列表
        :param fun:聚类算法名称
        :return: c语言结构体指针
        """
        f = os.path.join(BASE_DIR, "lib/" + fun)
        geneticToolDevelopment = ctypes.CDLL(f)

        row = len(matrix)
        col = len(matrix[0])

        # 将二维列表转为c语言二维数组
        temp = list()
        for i in matrix:
            a = (ctypes.c_float * len(i))(*i)
            temp.append(a)
        m = ((ctypes.c_float * col) * row)(*temp)

        if fun == "UPGMA":
            # 调用upgma函数，返回根节点指针
            geneticToolDevelopment.calDisMatrixToTreeByUPGMA.restype = ctypes.POINTER(self.Tree)  # 设置返回值为树指针
            tree = geneticToolDevelopment.calDisMatrixToTreeByUPGMA(m, ctypes.c_int(row),
                                                                    ctypes.c_int(col))  # 调用dll中upgma函数生成树结点指针
        elif fun == "NJ":
            # 调用nj函数，返回根节点指针
            geneticToolDevelopment.calDisMatrixToTreeByNJ.restype = ctypes.POINTER(self.Tree)  # 设置返回值为树指针
            tree = geneticToolDevelopment.calDisMatrixToTreeByNJ(m, ctypes.c_int(row),
                                                                 ctypes.c_int(col))  # 调用dll中nj函数生成树结点指针

        tree_tuple = self.__tree_post_order(tree)
        self.__count += 1

        return tree_tuple

    # 后序递归树节点，形成NWK元祖
    def __tree_post_order(self, tree):
        """
        :param tree:树节点指针
        :return: 叶节点直接返回值，中间节点返回左右子树合并起来的元组
        """
        if not tree.contents.left:
            b = tree.contents.i
            if self.__count == 1:
                self.sample_dis_dict[b] = tree.contents.dis
            return b
        else:
            tree_tup = (self.__tree_post_order(tree.contents.left), self.__tree_post_order(tree.contents.right))
            if self.__count == 1:
                a = self.__get_sample_tree_num(tree_tup)
                self.sample_dis_dict[a] = tree.contents.dis
            return tree_tup

    # 根据初始距离矩阵生成初始矩阵字典
    def get_sample_dict(self, sample_tree_tuple):
        if isinstance(sample_tree_tuple, tuple):
            a = self.__get_sample_tree_num(sample_tree_tuple)
            self.sample_dict_result[a] = 0
            self.sample_dict_start[a] = 0
            self.get_sample_dict(sample_tree_tuple[0])
            self.get_sample_dict(sample_tree_tuple[1])

    # 根据元组树生成唯一整数映射函数
    def __get_sample_tree_num(self, sample_tree_tuple):
        if isinstance(sample_tree_tuple, tuple):
            a = self.__get_sample_tree_num(sample_tree_tuple[0])
            b = self.__get_sample_tree_num(sample_tree_tuple[1])
        else:
            return PrimeNumberFun.get_prime_num(sample_tree_tuple) * (sample_tree_tuple + 1)
        return a + b

    # 根据生成的元组树去更新样本字典
    def update_dict(self, sample_tree_tuple):
        if isinstance(sample_tree_tuple, tuple):
            a = self.__get_sample_tree_num(sample_tree_tuple)
            if a in self.sample_dict_result.keys():
                self.sample_dict_result[a] += 1
            self.update_dict(sample_tree_tuple[0])
            self.update_dict(sample_tree_tuple[1])

    # 根据生成的元组树去更新样本字典,d更新目标字典
    def update_dict_d(self, sample_tree_tuple, d: dict):
        if isinstance(sample_tree_tuple, tuple):
            a = self.__get_sample_tree_num(sample_tree_tuple)
            if a in self.sample_dict_result.keys():
                d[a] += 1
            self.update_dict_d(sample_tree_tuple[0], d)
            self.update_dict_d(sample_tree_tuple[1], d)

    # 最终形成NWK字符串
    def get_Nwk_str(self, tree_tuple, times):
        """

        :param tree_tuple:元组树
        :param times:bootstrap次数
        :return: 递归形成NWK字符串
        """
        if isinstance(tree_tuple, tuple):
            s0 = self.get_Nwk_str(tree_tuple[0], times)
            s1 = self.get_Nwk_str(tree_tuple[1], times)
        else:
            return self.sample_names[tree_tuple] + ":" + str(self.sample_dis_dict[tree_tuple] / 2)

        tag = self.__get_sample_tree_num(tree_tuple)  # 记录当前树的标记
        st = "(" + s0 + "," + s1 + ")" + "%.1f" % (self.sample_dict_result[tag] / times * 100) + ":" \
             + str(self.sample_dis_dict[tag])
        return st

    # 获取存进excel格式的距离矩阵和相似矩阵
    def get_dis_sim_matrix(self, dis_matrix, fun):
        """
        :param dis_matrix: 传入的距离矩阵
        :param fun: bool类型，0为写入距离矩阵，1为写入相似矩阵
        :return: 返回excel表
        """

        l0 = list()
        l1 = ["sample"]
        l1.extend(self.sample_names)
        l0.append(l1)
        for i in range(0, len(dis_matrix)):
            l2 = [self.sample_names[i]]
            if fun == 0:
                l2.extend(dis_matrix[i])
            else:
                l3 = list()
                for j in dis_matrix[i]:
                    l3.append(1 - j)
                l2.extend(l3)
            l0.append(l2)
        return l0
